為了不浪費我白白畫的春聯,做了一個新年圖製造機
還沒有新年圖的可以到下面玩玩看~(快收假了不需要了吧!為什麼我的GA沒有數據QQ)
目前遇到的問題&沒有的功能:(有時間再解決...)
-手機版動不了!(Fabric.js無法拖拉圖片動不了)
-第一次進畫面好像圖片無法拖拉動不了(原因不明)重新整理後就可以
-即時Resize暫時無法即時偵測,不確定該怎麼設計各個canvas大小...所以目前都固定,只有手機和電腦板尺寸(還有bug就先抱歉了?)

開發內容:
主要元件
- Canvas 本體,主要操作邏輯 //NewYearCanvas.js檔案
- 剪裁圖片的彈跳視窗 //CustomModal檔案
開發/說明順序
文章上篇這邊請
- React & Bootstrap & Fabric.js 起手式 (文章上篇)
- 設定 Canvas 背景(文章上篇)
a. 讀取src
b. 設定長寬- 上傳圖片(文章上篇)
a. Modal開啟
b. 上傳圖片&讀取- 剪裁圖片 (<---您的位置在這裡)
a. 添加Clip Path
b. 裁切- 添加裁切好圖片到Canvas上
- 添加貼紙 (跟上一步驟5差不多)
- 移動順序
- 下載圖片
步驟:加上剪裁範圍→剪裁
先來加上剪裁範圍
const NewYearCanvas = (props)=>{
const [uploadClipPath, setUploadClipPath] = useState('')
//...以上省略
const addClip = ()=>{
				 //創造一個fabric的圓形      
          const  userClipPath = new fabric.Circle({
                top: 0,
                left: 0,
                radius: 50,
                width: 100,
                height: 100,
                fill: 'rgb(178, 178, 178, 0.4)',
            })
					//因為我想要正圓形,所以把可以上拉左拉的控制點都弄掉
            userClipPath.setControlsVisibility({
                mb: false,
                ml: false,
                mr: false,
                mt: false
            })
        
				//加到canvas上面
				//setActiveObject(userClipPath) -->讓他馬上被選取
        canvasModal.add(userClipPath).setActiveObject(userClipPath).renderAll();
				
				//存到state裡面
        setUploadClipPath(userClipPath)
    }
		//以下省略
}
export default NewYearCanvas
再來剪裁
讓使用者隨意移動位置,或是放大縮小圓裁切
裁切是利用 Fabric的clipPath參數,製作剪裁遮色片
clipPath的top & left 計算起點是圖片中心,所以要先計算出他的位置
再次算出我們拉的圓左上角起始點的位置,和被操作後的半徑
這邊難點在於計算圖片和剪裁圓形  放大縮小後的範圍,
1.算出圖片中心點 2. 算出剪裁圓的左上角

這邊直接說明:
const NewYearCanvas = (props)=>{
//...以上省略
const clipImage = ()=>{
				//算出圖片中心點起點相對於Canvas座標
				//getBoundingRect()取得相對於Canvas位置,但圖片都靠左上,就是top0,left0啦
				//圖片貼上canvas時,我有把他sacale to canvas的大小,
				//所以寬度/高度要乘上scale比例後才是正確的寬度高度,然後中心點就是寬高的一半
        const imageCenter = {
            top: uploadImage.getBoundingRect().top + uploadImage.height * uploadImage.scaleY / 2,
            left: uploadImage.getBoundingRect().left + uploadImage.width * uploadImage.scaleX / 2,
        }
       //stae裡面存好的上傳圖片直接拿出來用,設定剪裁參數
			//radius:(半徑剪裁圓的半徑*放大縮小的scale參數) 除以圖片放大縮小參數
			//算出剪裁圓起點的位置(相對於圖片中心)
			// 舉top(y座標)為例子:(剪裁圓對Canvas座標y - 圖片中心點座標)/(上傳圖片的縮放比例)
            uploadImage.set({
                clipPath: new fabric.Circle({
                    radius: uploadClipPath.radius* uploadClipPath.scaleX / uploadImage.scaleX,
                    top: (uploadClipPath.getBoundingRect().top - imageCenter
                        .top) / uploadImage.scaleY,
                    left: (uploadClipPath.getBoundingRect().left -
                        imageCenter
                        .left) / uploadImage.scaleX,
                }),
            });
      
        canvasModal.renderAll()
    }
		//以下省略
}
export default NewYearCanvas
如果說明不夠清楚歡迎留言!
另外我是參考這位大大的文章:
Day 26 - Fabricjs 進階自訂控制項
https://ithelp.ithome.com.tw/articles/10209056
把圖片裁切好之後,想要直接加上到Canvas上,
但問題來了,圖片被裁切掉的地方也會以透明方式出現,很難操作

我自己想到的方式是把在Modal上的Canvas寬高都變成跟剪裁的範圍一樣
先把圖片弄到左上角,再直接Canvas換寬度,就不用算圖片剪裁厚的座標啦
然後轉成圖片檔案再加上去底層的Canvas
const addPhoto = (stickerSrc)=>{
				//先檢查是否有被剪裁
        if (uploadClipPath) {
					//往左上角移動
					//移動距離就是剪裁圖片的座標,要負的
            uploadImage.set({
                top: -uploadClipPath.getBoundingRect().top,
                left: -uploadClipPath.getBoundingRect().left
            })
            canvasModal.renderAll()
						
					//置換Canvas的寬高
            canvasModal.setDimensions({
                width: uploadClipPath.getBoundingRect().width,
                height: uploadClipPath.getBoundingRect().height
            });
        }
        //把Canvas轉成圖片格式
            const modifiedImage = canvasModal.toDataURL("image/png").replace("image/png",
                "image/octet-stream");
				//下面把圖片貼上去底層的Canvas(存在state裡面)
            const pasteImage = new Image();
           
                pasteImage.src = modifiedImage;
                pasteImage.onload = function () {
                    const image = new fabric.Image(pasteImage);
                    image.set({
                        left: 100, //這邊隨意設定
                        top: 60,
                    });
                    canvas.add(image).setActiveObject(image).renderAll();
                    
                }
    }
		//以下省略
}
export default NewYearCanvas
貼紙一樣用fabric創造一個image然後貼上去
移動前後順序才不會有時候虎帽跑到人下面去啦
用fabric的函數: bringToFront() & sendToBack()
//圖片點擊就傳src回去
<img onClick={()=>addSticker(image.src)}/>
//添加貼紙
const addSticker = ()=>{
	const pasteImage = new Image();
            pasteImage.src = stickerSrc;
                pasteImage.onload = function () {
                    const image = new fabric.Image(pasteImage);
                    image.set({
                        left: 100,
                        top: 60,
                    });
                    canvas.add(image).setActiveObject(image).renderAll();
                }
//移動順序
const setOrder = (order)=>{
				//取得正被選取的物件
        const obj = canvas.getActiveObject()
        if(!obj) return
        if(order === 'top') obj.bringToFront()
        if(order === 'bottom') obj.sendToBack();
    }
把Canvas弄成圖片檔案,大功告成!
const output = ()=>{
        var image = canvas.toDataURL("image/png").replace("image/png",
                "image/octet-stream"
            ); 
            const a = document.createElement('a')
            a.href = image
            a.download = `newyear2020.jpeg`
            document.body.appendChild(a)
            a.click()
            document.body.removeChild(a)
    }
原始碼放在這邊:https://github.com/rachel-liaw/react_canvas
不過原始碼因為整個操作流程,會比文章多很多邏輯(多了button 開關、DOM邏輯等等...)
還菜菜的!
有術語上的錯誤、資料結構和寫法有任何建議請不吝指教!

過年後,就要把它變成我的快速圖片製造機(?)
再來加上繪圖功能、拖曳功能......(碎碎念)